﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Djs.Common.Components
{
    #region SizeRange
    /// <summary>
    /// Size range (Begin, End)
    /// </summary>
    public class SizeRange
    {
        #region Properties, constructor
        /// <summary>
        /// Create interval from begin and end. Booth can be null.
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        public SizeRange(Decimal? begin, Decimal? end)
        {
            this.Begin = begin;
            this.End = end;
        }
        /// <summary>
        /// Create interval from begin and time (size). Booth must be defined.
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="time"></param>
        public static SizeRange CreateFromBeginSize(Decimal begin, Decimal size)
        {
            return new SizeRange(begin, begin + (decimal)size);
        }
        /// <summary>
        /// Create interval from time (duration) and end. Booth must be defined.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="end"></param>
        public static SizeRange CreateFromSizeEnd(Decimal size, Decimal end)
        {
            return new SizeRange(end - (decimal)size, end);
        }
        public override int GetHashCode()
        {
            if (!this._Hashcode.HasValue)
                this._Hashcode = this.Begin.GetHashCode() ^ this.End.GetHashCode();
            return this._Hashcode.Value;
        }
        public override bool Equals(object obj)
        {
            return (this == (obj as SizeRange));
        }
        public override string ToString()
        {
            return this.Text;
        }
        /// <summary>
        /// Begin of interval
        /// </summary>
        public Decimal? Begin { get; private set; }
        /// <summary>
        /// Center of interval
        /// </summary>
        public Decimal? Center { get { return (this.IsFilled ? (Decimal?)(this.Begin.Value + this.Size.Value / 2m) : (Decimal?)null); } }
        /// <summary>
        /// End of interval
        /// </summary>
        public Decimal? End { get; private set; }
        /// <summary>
        /// Hashcode
        /// </summary>
        private int? _Hashcode;
        /// <summary>
        /// New empty instance, containg Begin = null and End = null
        /// </summary>
        public static SizeRange Empty { get { return new SizeRange(null, null); } }
        /// <summary>
        /// true, when Begin and End is null
        /// </summary>
        public bool IsEmpty { get { return (!this.Begin.HasValue && !this.End.HasValue); } }
        /// <summary>
        /// true, when Begin and End has value
        /// </summary>
        public bool IsFilled { get { return (this.Begin.HasValue && this.End.HasValue); } }
        /// <summary>
        /// true, when Begin has value ane End has not value
        /// </summary>
        public bool HasOnlyBegin { get { return this.Begin.HasValue && !this.End.HasValue; } }
        /// <summary>
        /// true, when End has value ane Begin has not value
        /// </summary>
        public bool HasOnlyEnd { get { return this.End.HasValue && !this.Begin.HasValue; } }
        /// <summary>
        /// Time from Begin to End (when IsFilled), or null.
        /// </summary>
        public Decimal? Size { get { return (this.IsFilled ? (Decimal?)(this.End.Value - this.Begin.Value) : (Decimal?)null); } }
        /// <summary>
        /// New() instance SizeRange, containing current values of this instance
        /// </summary>
        public SizeRange Clone { get { return new SizeRange(this.Begin, this.End); } }
        public string Text
        {
            get
            {
                if (this.IsFilled)
                    return this.Begin.Value.ToString() + " ÷ " + this.End.Value.ToString();
                if (this.HasOnlyBegin)
                    return this.Begin.Value.ToString() + " ÷ ???";
                if (this.HasOnlyEnd)
                    return "??? ÷ " + this.End.Value.ToString();
                return "???";
            }
        }
        #endregion
        #region Operators
        public static SizeRange operator *(SizeRange a, SizeRange b)
        {
            bool an = (a == null || !a.IsFilled);
            bool bn = (b == null || !b.IsFilled);
            if (an && bn) return SizeRange.Empty;
            if (bn) return a.Clone;
            if (an) return b.Clone;
            Decimal? begin = _Max(a.Begin, b.Begin);
            Decimal? end = _Min(a.End, b.End);
            if (begin.HasValue && end.HasValue && begin.Value > end.Value) return SizeRange.Empty;
            return new SizeRange(begin, end);
        }
        public static SizeRange operator +(SizeRange a, SizeRange b)
        {
            bool an = (a == null || !a.IsFilled);
            bool bn = (b == null || !b.IsFilled);
            if (an && bn) return SizeRange.Empty;
            if (bn) return a.Clone;
            if (an) return b.Clone;
            Decimal? begin = _Min(a.Begin, b.Begin);
            Decimal? end = _Max(a.End, b.End);
            return new SizeRange(begin, end);
        }
        public static bool operator ==(SizeRange a, SizeRange b)
        {
            bool an = ((object)a == null);
            bool bn = ((object)b == null);
            if (an && bn) return true;
            if (an || bn) return false;
            return (_Equal(a.Begin, b.Begin) && _Equal(a.End, b.End));
        }
        public static bool operator !=(SizeRange a, SizeRange b)
        {
            bool an = ((object)a == null);
            bool bn = ((object)b == null);
            if (an && bn) return false;
            if (an || bn) return true;
            return (!_Equal(a.Begin, b.Begin) || !_Equal(a.End, b.End));
        }
        /// <summary>
        /// Return true, when a has equal value to b, or when a and b has not value.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static bool _Equal(Decimal? a, Decimal? b)
        {
            if (a.HasValue && b.HasValue) return (a.Value == b.Value);
            if (a.HasValue || b.HasValue) return false;
            return true;
        }
        /// <summary>
        /// Return min value of a, b. 
        /// When a has not value, then return b (and vice versa).
        /// Return null, when a and b has not value.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static Decimal? _Min(Decimal? a, Decimal? b)
        {
            if (a.HasValue && b.HasValue)
                return (a.Value < b.Value ? a.Value : b.Value);
            if (a.HasValue)
                return a;
            if (b.HasValue)
                return b;
            return null;
        }
        /// <summary>
        /// Return max value of a, b. 
        /// When a has not value, then return b (and vice versa).
        /// Return null, when a and b has not value.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static Decimal? _Max(Decimal? a, Decimal? b)
        {
            if (a.HasValue && b.HasValue)
                return (a.Value > b.Value ? a.Value : b.Value);
            if (a.HasValue)
                return a;
            if (b.HasValue)
                return b;
            return null;
        }
        #endregion
        #region Public methods - Zoom
        /// <summary>
        /// Returns a new instance created from current instance, which Time is (ratio * this.Time) and center of zooming is on specified date.
        /// </summary>
        /// <param name="center"></param>
        /// <param name="ratio"></param>
        /// <returns></returns>
        public SizeRange ZoomToRatio(Decimal center, double ratio)
        {
            return ZoomToRatio(center, (decimal)ratio);
        }
        /// <summary>
        /// Returns a new instance created from current instance, which Time is (ratio * this.Time) and center of zooming is on specified date.
        /// </summary>
        /// <param name="center"></param>
        /// <param name="ratio"></param>
        /// <returns></returns>
        public SizeRange ZoomToRatio(Decimal center, decimal ratio)
        {
            if (!this.IsFilled) return Empty;
            if (ratio == 1.0m) return this.Clone;
            Decimal distance = (center - this.Begin.Value) * ratio;
            Decimal begin = center - distance;
            Decimal size = this.Size.Value * ratio;
            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Returns a new instance created from current instance, which Time is specified and center of zooming is on specified date.
        /// </summary>
        /// <param name="center"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        public SizeRange ZoomToSize(Decimal center, decimal size)
        {
            if (!this.IsFilled) return Empty;
            if (size == this.Size.Value) return this.Clone;
            decimal position = (center - this.Begin.Value) / this.Size.Value;  // Relative position of "center" on current SizeRange
            Decimal begin = center - (size * position);                        // New Begin = center minus (center position on new Time)
            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Returns a new instance created from current instance, which Time is (ratio * this.Time) and center of zooming is on specified relative position.
        /// </summary>
        /// <param name="relativePivot"></param>
        /// <param name="ratio"></param>
        /// <returns></returns>
        public SizeRange ZoomToSize(double relativePivot, decimal size)
        {
            if (!this.IsFilled) return Empty;
            if (size == this.Size.Value) return this.Clone;
            Decimal center = this.GetValueAt(relativePivot).Value;
            Decimal distance = (decimal)relativePivot * size;
            Decimal begin = center - distance;
            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Returns a date on relative position (where 0 = Begin, 1 = End). Center of interval is on position 0.5d.
        /// When this is not filled, return null.
        /// </summary>
        /// <param name="relativePosition"></param>
        /// <returns></returns>
        public Decimal? GetValueAt(double relativePosition)
        {
            if (!this.IsFilled) return null;
            decimal distance = (decimal)relativePosition * this.Size.Value;
            return this.Begin.Value + distance;
        }
        #endregion
        #region Static services (ToText, Round)
        /// <summary>
        /// Round specified Decimal value to nearest whole value in specified interval.
        /// In example, origin = 16518.354 round = 5.00, mode = Floor; result = 16515.000
        /// </summary>
        /// <param name="origin">Original Decimal</param>
        /// <param name="round">Round divisor (amount, to which will be original Decimal rounded)</param>
        /// <param name="mode">Round mode</param>
        /// <returns>Rounded Decimal</returns>
        public static Decimal RoundValue(Decimal origin, Decimal round, RoundMode mode)
        {
            Decimal result = origin;
            if (round > 0m)
            {
                Decimal count = origin / round;
                switch (mode)
                {
                    case RoundMode.Floor:
                        count = Math.Floor(count);
                        break;
                    case RoundMode.Math:
                        count = Math.Round(count, 0, MidpointRounding.AwayFromZero);
                        break;
                    case RoundMode.Ceiling:
                        count = Math.Ceiling(count);
                        break;
                }
                result = count * round;
            }
            return result;
        }
        #endregion
    }
    #endregion
}
